package cn.com.w.redis.use.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

/**
 * 基于redis全局唯一ID生成器
 * 优点：1.唯一（不必说了）
 * 2.单调递增（天然排序、分页优势）
 * 4.不依赖与关系型数据库，灵活方便。
 * 3.sql索引的查询效率，long类型的值比String的查询效率高。
 *
 * @author zhangxl
 * @date 2022/3/24
 */
@Component
public class RedisIdGenerator {

    /**
     * 自定义的一个开始时间戳2022-01-05 00:00:00
     */
    private final static long BEGIN_TIMESTAMP = 1641340800L;
    /**
     * 步长/变化数量：一般情况集群有多台redis那么有几台步长就设置多少
     */
    private final static long DELTA = 1L;
    /**
     * 序列号的一个位数。
     */
    private final static int COUNT_BITS = 32;
    /**
     * 时间格式
     */
    private final static String PATTERN = "yyyy:MM:dd";


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 获取下一个ID值
     *
     * @param keyPrefix
     * @return
     */
    public long nextId(String keyPrefix) {
        //1.高位值，获取当前系统时间与指定时间的时间戳差值
        LocalDateTime now = LocalDateTime.now();
        long nowTime = now.toEpochSecond(ZoneOffset.UTC);
        long nowTimeStamp = nowTime - BEGIN_TIMESTAMP;
        //2.低位值
        //2.1获取key的时间规则
        String sTime = now.format(DateTimeFormatter.ofPattern(PATTERN));
        //2.2 获取redis的单点自增值,如果key不存在，那么会新增一个key作为起始值。1个redis的时候方式一
        //long endTimesTamp = stringRedisTemplate.opsForValue().increment("idIncr:" + keyPrefix + ":" + sTime);
        //2.2 如果是集群下有多台redis那么有几台步长就设置多少。
        long endTimesTamp = stringRedisTemplate.opsForValue().increment("idIncr:" + keyPrefix + ":" + sTime, DELTA);
        //3.拼接返回，数值的一种拼接方式，把时间戳移动到高位，位运算、或运算。
        return nowTimeStamp << COUNT_BITS | endTimesTamp;
    }

    public static void main(String[] args) {
        //设置指定日期2222-01-05 00:00:00
        LocalDateTime dateTime = LocalDateTime.of(2022, 1, 5, 0, 0, 0);
        //获取秒的时间戳，UTC-现世界标准时间
        long second = dateTime.toEpochSecond(ZoneOffset.UTC);
        System.out.println("second=" + second);
    }
}


